package digital_cash_system.shop;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Date;

import digital_cash_system.Coin;
import digital_cash_system.Logger;
import digital_cash_system.PublishedHash;
import digital_cash_system.Utils;
import digital_cash_system.bank.BankBase;
import digital_cash_system.bank.Publishedinfo;

public class Shop extends ShopBase
{
	int shopId;

	private BigInteger p;
	private BigInteger q;
	private BigInteger g;
	private BigInteger g1;
	private BigInteger g2;
	private BigInteger h;

	private BigInteger h_u;
	private BigInteger u1;

	private BigInteger z;

	private ArrayList<DepositInfo> coins = new ArrayList<DepositInfo>();

	public Shop(int shopId)
	{
		this.shopId = shopId;
	}

	public void openAccount()
	{
		try
		{
			Socket clientSocket = new Socket(BankBase.BankAddr, BankBase.Port);
			ObjectOutputStream outputStream = new ObjectOutputStream(clientSocket.getOutputStream());
			// --- send request type
			outputStream.writeObject(BankBase.RequestType.OpenAccount);
			outputStream.flush();
			// ---

			ObjectInputStream inputStream = new ObjectInputStream(clientSocket.getInputStream());
			// --- read published parms
			Publishedinfo publishedinfo = (Publishedinfo) inputStream.readObject();
			p = publishedinfo.getP();
			q = publishedinfo.getQ();
			g = publishedinfo.getG();
			g1 = publishedinfo.getG1();
			g2 = publishedinfo.getG2();
			h = publishedinfo.getH();
			// ---

			BigInteger m;
			do
			{
				u1 = Utils.generateZ_qMember(q);
				h_u = g1.modPow(u1, p);
				m = h_u.multiply(g2).mod(p);
			} while (m.equals(BigInteger.ONE));

			// send id h_u;
			outputStream.writeObject(h_u);

			// --- Logger output
			Logger.getInstance().write("Send to Bank h_u = " + h_u);
			// ---

			// z=ReceiveSignature
			z = (BigInteger) inputStream.readObject();

			// --- Logger output
			Logger.getInstance().write("Receive from Bank z = " + z);
			// ---

			// --- Logger output
			Logger.getInstance().write("Account opened successfully");
			// ---

			inputStream.close();
			outputStream.close();
			clientSocket.close();
		} catch (IOException | ClassNotFoundException e)
		{
			e.printStackTrace();
		}
	}

	public void deposit()
	{
		try
		{
			Socket clientSocket = new Socket(BankBase.BankAddr, BankBase.Port);
			ObjectOutputStream outputStream;
			outputStream = new ObjectOutputStream(clientSocket.getOutputStream());
			// --- send request type
			outputStream.writeObject(BankBase.RequestType.Deposit);
			outputStream.flush();
			// ---

			// send id
			outputStream.writeInt(shopId);

			// --- Logger output
			Logger.getInstance().write("Send to Bank shopID = " + shopId);
			// ---

			// send coin, r1, r2, dateTime
			DepositInfo depositInfo = coins.remove(0);
			outputStream.writeObject(depositInfo);

			// --- Logger output
			Logger.getInstance().write("Send to Bank r1 = " + depositInfo.getR1());
			// --

			// --- Logger output
			Logger.getInstance().write("Send to Bank r2 = " + depositInfo.getR2());
			// --

			// --- Logger output
			Logger.getInstance().write("Send to Bank dateTime = " + depositInfo.getDateTime());
			// --

			// --- Logger output
			Logger.getInstance().write("Deposit completed successfully");
			// ---

			outputStream.close();
			clientSocket.close();
		} catch (IOException e)
		{
			e.printStackTrace();
		}
	}

	protected void onPay(Socket clientSocket, ObjectInputStream inputStream)
	{
		try
		{
			ObjectOutputStream outputStream = new ObjectOutputStream(clientSocket.getOutputStream());

			// receive coin
			Coin coin = (Coin) inputStream.readObject();

			Date dateTime = new Date();
			BigInteger d = PublishedHash.hashH0(coin.getA(), coin.getB(), shopId, dateTime, q);
			// send d
			outputStream.writeObject(d);

			// --- Logger output
			Logger.getInstance().write("Send to Customer d = " + d);
			// ---

			// receive r1 r2
			BigInteger r1 = (BigInteger) inputStream.readObject();
			BigInteger r2 = (BigInteger) inputStream.readObject();
			BigInteger c_ = PublishedHash.hashH(coin.getA(), coin.getB(), coin.getZ_(),
					coin.getA_(), coin.getB_(), q);

			// --- Logger output
			Logger.getInstance().write("Receive from Customer r1 = " + r1);
			// ---

			// --- Logger output
			Logger.getInstance().write("Receive from Customer r2 = " + r2);
			// ---

			boolean acceptCoin1 = coin.getA().modPow(d, p).multiply(coin.getB()).mod(p)
					.equals(g1.modPow(r1, p).multiply(g2.modPow(r2, p)).mod(p));

			boolean acceptCoin2 = g.modPow(coin.getR_(), p).equals(
					coin.getA_().multiply(h.modPow(c_, p)).mod(p));

			boolean acceptCoin3 = coin.getA().modPow(coin.getR_(), p)
					.equals(coin.getB_().multiply(coin.getZ_().modPow(c_, p)).mod(p));

			if (acceptCoin1 && acceptCoin2 && acceptCoin3)
			{

				// TODO check double spending etc coin
				// TODO accept coin
				coins.add(new DepositInfo(coin, r1, r2, dateTime));

				// --- Logger output
				Logger.getInstance().write("Payment completed successfully");
				// ---
			}
		} catch (IOException | ClassNotFoundException e)
		{
			e.printStackTrace();
		}
	}
}
